#!/usr/bin/ruby

# /* LICENSE:
#   =========================================================================
#     CMPack'03 Source Code Release for OPEN-R SDK v1.0
#     Copyright (C) 2003 Multirobot Lab [Project Head: Manuela Veloso]
#     School of Computer Science, Carnegie Mellon University
#     All rights reserved.
#   ========================================================================= */

require 'getoptlong'

$debug = true

$usage = "usage: setitup
options:
-h|--help                   get help
";

$diff_width = 2
$ind_width  = 4
$side_width = 40

def ignore_file?(filename)
  return (filename =~ /^\.\.?$/   || 
          filename =~ /^CVS$/     ||
          filename =~ /cvs.locks/ ||
          filename =~ /\~$/         )
end

def opt_str_to_s(opt_str)
  if(opt_str.nil?)
    ""
  else
    opt_str
  end
end

$base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
def base64_encode(num,bits)
  out_str = ""
  while(num > 0 && bits > 0)
    remain = num % 64
    out_str = $base64_chars[remain].chr + out_str
    num /= 64
    bits -= 6
  end
  while(bits > 0)
    out_str = $base64_chars[0].chr + out_str
    bits -= 6
  end
  return out_str
end

class InvalidIndicatorError < ArgumentError
end

class ConfigFileSetting
  attr_accessor :name
  attr_accessor :value
  attr_accessor :comment
  attr_accessor :white_space # array of white space before/after each component
  def output_format()
    out = ""
    out += opt_str_to_s(@white_space[0])
    out += opt_str_to_s(@name          )
    out += opt_str_to_s(@white_space[1])
    out += "="
    out += opt_str_to_s(@white_space[2])
    out += opt_str_to_s(@value         )
    out += opt_str_to_s(@white_space[3])
    out += ";"
    out += opt_str_to_s(@white_space[4])
    out += opt_str_to_s(@comment       )
    out += "\n"
    return out
  end
  def clone()
    cfs = ConfigFileSetting.new()
    cfs.name  = @name.clone()
    cfs.value = @value.clone()
    cfs.comment = (@comment.nil? ? nil : @comment.clone())
    cfs.white_space = @white_space.collect {|ws|
      (ws.nil? ? nil : ws.clone())
    }
    cfs
  end
end

class ConfigFile
  attr_accessor :filename  # includes full path
  attr_accessor :settings
  # enough info to reconstruct the source file if no fields are changed
  attr_accessor :source_save
  def initialize()
    @filename = ""
    @settings = []
  end
  def read(filename)
    @filename = filename
    @settings = []
    @source_save = []
    
    config_file = File.new(@filename)
    while(line=config_file.gets())
      comment = nil
      base_line = line
      if(idx=line.index(%r%//.*$%))
        comment = line[idx..-1]
        comment.chomp!()
        base_line = line[0,idx]
      end
      case base_line
        #            field_name         value
      when %r%^(\s*)([^= ]+)(\s*)=(\s*)(\S+)(\s*);(\s*)$%
        setting = ConfigFileSetting.new()
        setting.name    = $2
        setting.value   = $5
        setting.comment = comment
        setting.white_space = [$1, $3, $4, $6, $7]
        @settings.push setting
        @source_save.push setting
      else
        @source_save.push line
      end
    end
  end
  def write()
    file = File.new(@filename,"w")
    @source_save.each {|setting|
      case setting
      when ConfigFileSetting
        printf file,"%s",setting.output_format
      when String
        printf file,"%s",setting
      end
    }
    file.close()
  end
  def value(field_name)
    setting = @settings.find() {|set|
      set.name == field_name
    }
    if(setting)
      return setting.value
    else
      return nil
    end
  end
  def set_value(field_name,new_value)
    setting = @settings.find() {|set|
      set.name == field_name
    }
    if(setting)
      return (setting.value=new_value)
    else
      return nil
    end
  end
  def get_setting(field_name)
    @settings.find() {|set|
      set.name == field_name
    }
  end
  def add_setting(setting)
    @settings.push setting
    @source_save.push setting
  end
  def copy(other_config_file)
    @settings    = other_config_file.settings
    @source_save = other_config_file.source_save
  end
  def clone()
    cf = ConfigFile.new()
    cf.filename = @filename
    cf.source_save = @source_save.collect {|ss|
      ss.clone()
    }
    cf.settings = cf.source_save.collect {|ss|
      case ss
      when ConfigFileSetting
        ss
      else
        nil
      end
    }
    cf.settings.reject! {|s|
      s.nil?
    }
    cf
  end
  def each()
    @settings.each() {|setting|
      yield setting
    }
  end
end

################################################################################
# OptionSkel heirarchy
################################################################################

class ShortNestError < SyntaxError
end

class OptionType
  File  = 0
  Field = 1
end

class OptionSkel # Option skeleton
  attr_accessor :path  # relative path
  attr_accessor :nest_depth # nesting depth
  def OptionSkel.remove_nest(line,depth)
    #print "remove_nest depth=#{depth} line='#{line}'\n" if($debug)
    while(depth > 0)
      if(line =~ /^\s*\.\s*(.*)$/)
        line = $1
        depth -= 1
        #print " remove_nest depth=#{depth} line='#{line}'\n" if($debug)
      else
        raise ShortNestError.new("not enough nesting, short #{depth} levels")
      end
    end
    line
  end
  def OptionSkel.read(file,nest_depth)
    opt = nil
    old_pos = file.pos
    line = file.gets
    line = OptionSkel.remove_nest(line,nest_depth)
    #print "found option line '#{line}'\n" if($debug)
    if(line =~ /^\s*option\s+(\S+.*)/)
      type = $1
      #print "valid option line, type='#{type}'\n" if($debug)
      file.pos = old_pos
      case type
      when /^group/
        opt = OptionGroupSkel.read(file,nest_depth)
      when /^dir/
        opt = OptionDirSkel.read  (file,nest_depth)
      when /^file(\S*)\s+(\S+)/
        type     = $1
        filename = $2

        if(type!="config" && type!="atom")
          if(type!="")
            raise SyntaxError("invalid option line '#{line}', file#{type} not valid file type")
          end
          case filename
          when /\.cfg$/
            type = "config"
          else
            type = "atom"
          end
        end

        case type
        when "config"
          opt = OptionConfigFileSkel.read(file,nest_depth)
        when "atom"
          opt = OptionAtomFileSkel.read  (file,nest_depth)
        else
          raise NotImplementError("shouldn't have got a bad file type, but did")
        end
      end
    else
      print "raising syntax error\n" if($debug)
      raise SyntaxError.new("invalid option line '#{line}'")
    end

    return opt
  end
  def initialize(path)
    @path = path
  end
end

class OptionAtomSkel < OptionSkel
  attr_accessor :type
  def initialize(path, type) 
    super(path)
    @type = type
  end
end

class OptionAtomFileSkel < OptionAtomSkel
  def initialize(path)
    super(path,OptionType::File)
  end
  def OptionAtomFileSkel.read(file,nest_depth)
    opt = nil
    line = file.gets()
    line = OptionSkel.remove_nest(line,nest_depth)
    if(line =~ /^\s*option\s+file(?:atom)?\s+(\S+)/)
      path = $1
      nest_depth.times {print "  "}
      print "found atomic file option #{path}\n" if($debug)
      opt = OptionAtomFileSkel.new(path)
    else
      raise SyntaxError.new("expected an option file line got '#{line}' instead at nest depth #{nest_depth}")
    end

    return opt
  end
end

class OptionAtomFieldSkel < OptionAtomSkel
  attr_accessor :field_name
  def initialize(path, field_name)
    super(path,OptionType::Field)
    @field_name = field_name
  end
end

class OptionGroupSkel < OptionSkel
  attr_accessor :name
  attr_accessor :opts
  # the next thing in the open File must be the option group to read
  def OptionGroupSkel.read(file,nest_depth)
    opt = OptionGroupSkel.new("./")
    opt.nest_depth = nest_depth
    line = file.gets
    line = OptionSkel.remove_nest(line,nest_depth)
    if(line =~ /^\s*option\s+group\s+"([^"]*)"/)
      opt.name = $1
      nest_depth.times {print "  "}
      print "found group option #{opt.name}\n"
    else
      raise SyntaxError.new("error processing option group at nesting level #{nest_depth}, line was '#{line}'")
    end

    opt.read_sub_opts(file,nest_depth)

    return opt
  end
  # nest_depth is nesting depth of parent
  def read_sub_opts(file,nest_depth)
    @opts = []
    old_pos = file.pos
    begin
      while(!file.eof?)
        @opts.push OptionSkel.read(file,nest_depth+1)

        old_pos = file.pos
      end
    rescue ShortNestError
      file.pos = old_pos
    end
  end
end

class OptionConfigFileSkel < OptionGroupSkel
  def OptionConfigFileSkel.read(file,nest_depth)
    opt = OptionConfigFileSkel.new("./")
    opt.nest_depth = nest_depth
    line = file.gets
    #print "read line '#{line}'\n"
    line = OptionSkel.remove_nest(line,nest_depth)
    if(line =~ /^\s*option\s+file\s+(\S+)/)
      opt.name = $1
      opt.path = $1
      if($debug)
        nest_depth.times {print "  "}
        print "found config file option #{opt.name}\n"
      end
    else
      raise SyntaxError.new("error processing option group at nesting level #{nest_depth}")
    end

    opt.read_sub_opts(file,nest_depth)

    return opt
  end
end

class OptionDirSkel < OptionGroupSkel
  def OptionDirSkel.read(file,nest_depth)
    opt = OptionDirSkel.new("./")
    opt.nest_depth = nest_depth
    line = file.gets
    line = OptionSkel.remove_nest(line,nest_depth)
    if(line =~ /^\s*option\s+dir\s+(\S+)/)
      opt.name = $1
      opt.path = $1
      if($debug)
        nest_depth.times {print "  "}
        print "found dir option #{opt.path}\n"
      end
    else
      raise SyntaxError.new("error processing option group at nesting level #{nest_depth}")
    end

    opt.read_sub_opts(file,nest_depth)

    return opt
  end
end

################################################################################
# Option hierarchy for actual values of all configuration items
################################################################################

class Option
  attr_accessor :opt_skel
  attr_accessor :full_path     # absolute path to data item
  attr_accessor :rel_path      # path to data item relative to parent
  attr_accessor :nest_depth    # nesting depth
  def Option.create(parent,cur_path,my_rel_path,opt_skel)
    print "Option.create called with (#{parent}, #{cur_path}, #{my_rel_path}, #{opt_skel.nil? ? "nil" : "skel"})\n"
    opt = nil
    if(my_rel_path.nil? && !opt_skel.nil?)
      my_rel_path = opt_skel.path
    end
    full_path = if(my_rel_path.nil?)
                  cur_path
                else
                  File.join(cur_path,my_rel_path)
                end
    case opt_skel
    when OptionDirSkel
      opt = OptionDir.new       ()
    when OptionConfigFileSkel
      opt = OptionConfigFile.new()
    when OptionGroupSkel
      opt = OptionGroup.new     ()
    when OptionAtomFileSkel
      opt = OptionAtomFile.new  ()
    when OptionAtomFieldSkel
      opt = OptionAtomField.new ()
    when OptionAtomSkel
      opt = OptionAtom.new      ()
    when OptionSkel
      opt = Option.new          ()
    when NilClass
      if(OptionAtomField.field_path?(full_path))
        opt = OptionAtomField.new()
      else
        case File.ftype(full_path)
        when "directory"
          opt = OptionDir.new()
        when "file"
          case File.basename(full_path)
          when /\.cfg$/
            opt = OptionConfigFile.new()
          else
            opt = OptionAtomFile.new()
          end
        end
      end
    end
    opt.full_path  = full_path
    opt.rel_path   = my_rel_path
    opt.opt_skel    = opt_skel
    opt.nest_depth = if(@opt_skel.nil?)
                       if(parent.nil?)
                         0
                       else
                         parent.nest_depth + 1
                       end
                     else
                       @opt_skel.nest_depth
                     end
    opt.fill(parent)

    return opt
  end
  def set(path)
    @path = path
  end
  def fill(parent)
    print "filling option #{@full_path}\n" if($debug)
  end
  def copy(opt)
  end
  def copy_ind(opt,ind)
    if(ind == "")
      copy(opt)
    end
  end
  def clone_copy(opt)
    @opt_skel   = (opt.opt_skel.nil? ? nil : opt.opt_skel.clone())
    @full_path  = opt.full_path.clone()
    @rel_path   = opt.rel_path.clone()
    @nest_depth = opt.nest_depth
  end
  def clone()
    opt = Option.new
    opt.clone_copy(self)
    opt
  end
  def change_path(path)
    @full_path = File.join(path,@rel_path)
  end
  def nest_string()
    out_str = ""
    @nest_depth.times {out_str += "  "}
    out_str
  end
  def debug_field_string()
    "skel=" + (@opt_skel.nil? ? "no" : "yes") + " full_path=" + @full_path + " rel_path=" + @rel_path
  end
  def debug_print()
    out_str = nest_string()
    out_str += self.class.to_s
    out_str += " "
    out_str += debug_field_string()
    print out_str,"\n"
  end
  def pretty_print_string()
    @rel_path
  end
  def pretty_print()
    out_str = nest_string()
    out_str += pretty_print_string()
    print out_str,"\n"
  end
  def diff(other_opt)
    if(other_opt.nil?)
      return true
    end
    return (@rel_path != other_opt.rel_path)
  end
  def count_diffs(other_opt)
    return (diff(other_opt) ? 1 : 0)
  end
  def print_pair(other_opt,indicator="a",reverse=false)
    out_str = nest_string()
    out_str += pretty_print_string()
    other_out_str = ""
    if(!other_opt.nil?)
      other_out_str = other_opt.nest_string()
      other_out_str += other_opt.pretty_print_string()
    end
    diff_count = count_diffs(other_opt)
    diff_count_str = (diff_count != 0 ? sprintf("%#{$diff_width.to_s}d",diff_count) : "")
    if(reverse)
      printf("%-#{$ind_width.to_s}s %#{$diff_width.to_s}s %-#{$side_width.to_s}s %-#{$side_width.to_s}s\n",
             indicator,diff_count_str,other_out_str,out_str)
    else
      printf("%-#{$ind_width.to_s}s %#{$diff_width.to_s}s %-#{$side_width.to_s}s %-#{$side_width.to_s}s\n",
             indicator,diff_count_str,out_str,other_out_str)
    end
  end
  def write()
  end
end

class OptionAtom < Option
  attr_accessor :type
  def set(path, type) 
    super(path)
    @type = type
  end
  def debug_field_string()
    super() + " type=" + (type==OptionType::File ? "File" : "Field")
  end
  def clone_copy(opt)
    super(opt)
    @type = opt.type
  end
  def clone()
    opt = OptionAtom.new
    opt.clone_copy(self)
    opt
  end
end

class OptionAtomFile < OptionAtom
  attr_accessor :present
  attr_accessor :md5sum
  attr_accessor :data_file
  def initialize()
    @data_file=""
  end
  def fill(parent)
    print "filling option atom file #{@full_path}\n" if($debug)
    @type = OptionType::File
    begin
      File.stat(@full_path)
      @present = true
      @md5sum = `md5sum #{@full_path}`
      @md5sum = @md5sum.split(/\s/)[0].hex
      uniquifier = @full_path.clone
      uniquifier.gsub!(%r%/%,"")
      @data_file = "/tmp/setitup#{sprintf("%x",@md5sum)}#{uniquifier}"
      data_file_clone = @data_file.clone
      ObjectSpace::define_finalizer(@data_file,proc {File.delete(data_file_clone)} )
      print("rsync -t #{@full_path} #{@data_file}\n");
      system("rsync -t #{@full_path} #{@data_file}");
    rescue Errno::ENOENT
      @present = false
      @md5sum = -1
    end
  end
  def set(path)
    super(path,OptionType::File)
  end
  def copy(other_opt)
    if(@data_file != "")
      if(other_opt.present && @md5sum != other_opt.md5sum)
        system("rsync -t #{other_opt.data_file} #{@data_file}")
        @md5sum    = other_opt.md5sum
        @present = true
      end
    else
      @md5sum    = other_opt.md5sum
      @data_file = other_opt.data_file
    end
  end
  def clone_copy(opt)
    super(opt)
    @present   = opt.present
    @md5sum    = opt.md5sum
    @data_file = opt.data_file
  end
  def clone()
    opt = OptionAtomFile.new
    opt.clone_copy(self)
    opt
  end
  def change_path(path)
    @full_path = File.join(path,@rel_path)
    uniquifier = @full_path.clone
    uniquifier.gsub!(%r%/%,"")
    new_data_file = "/tmp/setitup#{sprintf("%x",@md5sum)}#{uniquifier}"
    print("rsync -t #{@data_file} #{new_data_file}\n");
    system("rsync -t #{@data_file} #{new_data_file}");
    @data_file = new_data_file
    data_file_clone = @data_file.clone
    ObjectSpace::define_finalizer(@data_file,proc {File.delete(data_file_clone)})
    ObjectSpace::garbage_collect()
  end
  def diff(other_opt)
    return (super(other_opt) ||
            @rel_path != other_opt.rel_path ||
            @md5sum   != other_opt.md5sum)
  end
  def debug_field_string()
    super() + " present=" + @present.to_s
  end
  def pretty_print_string()
    sprintf("%-12s %s",File.basename(@rel_path),base64_encode(@md5sum,128))
  end
  def write()
    print("rsync -t #{@data_file} #{@full_path}\n")
    system("rsync -t #{@data_file} #{@full_path}")
  end
end

class OptionAtomField < OptionAtom
  attr_accessor :field_name
  attr_accessor :config_file
  attr_accessor :setting
  def OptionAtomField.field_path?(full_path)
    if(full_path =~ /^(.*)&([^&]*)/)
      return true
    else
      return false
    end
  end
  def OptionAtomField.split_path(full_path)
    if(full_path !~ /^(.*)&([^&]*)/)
      raise ArgumentError.new("invalid full path for option atom field '#{@full_path}'")
    end
    [$1,$2]
  end
  def OptionAtomField.join_path(config_file_path,field_name)
    return config_file_path + "&" + field_name
  end
  def fill(parent)
    print "filling option atom field #{@full_path}\n" if($debug)
    @type = OptionType::Field
    @full_path, @field_name = OptionAtomField.split_path(@full_path)
    @rel_path = @field_name
    @config_file = parent.config_file
    @setting = @config_file.get_setting(@field_name)
  end
  def set(path, field_name)
    super(path,OptionType::Field)
    @field_name = field_name
  end
  def copy(other_opt)
    super(other_opt)
    field_value = other_opt.config_file.value(@field_name)
    if(field_value)
      @config_file.set_value(@field_name,field_value)
    end
  end
  def set_config_file(config_file)
    @config_file = config_file
    @setting     = @config_file.get_setting(@field_name)
  end
  def clone_copy(opt)
    super(opt)
    @field_name  = opt.field_name.clone()
    @config_file = nil
    @setting = nil
  end
  def clone()
    opt = OptionAtomField.new
    opt.clone_copy(self)
    opt
  end
  def change_path(path)
    @full_path = path + "&" + @rel_path
  end
  def diff(other_opt)
    return (super(other_opt) ||
            @field_name    != other_opt.field_name ||
            @setting.value != other_opt.setting.value)
  end
  def debug_field_string()
    field_value = @config_file.value(@field_name)
    super() + " field_name=" + @field_name + " value=" + field_value.to_s
  end
  def pretty_print_string()
    #print "config file\n"
    #p @config_file
    #print "setting\n"
    #p @setting
    field_value = @config_file.value(@field_name).to_s
    "#{@field_name}=#{field_value}"
  end
end

class OptionGroup < Option
  attr_accessor :name
  attr_accessor :opts
  def set()
    super()
    @name = @opt_skel.name
    @opts = nil
  end
  def fill(parent)
    print "filling option group #{@full_path}\n" if($debug)
    @name = @opt_skel.name
    @opts = @opt_skel.opts.collect {|opt_skel_iter|
      Option.create(self,@full_path,nil,opt_skel_iter)
    }
  end
  def copy(other_opt)
    super(other_opt)
    opt_hash = Hash.new()
    if(other_opt)
      other_opt.opts.each {|opt|
        opt_hash[opt.rel_path] = opt
      }
    end
    @opts.each {|opt|
      other_opt = opt_hash[opt.rel_path]
      if(other_opt)
        opt.copy(other_opt)
      end
    }
  end
  def copy_ind(other_opt,ind)
    if(ind == "")
      copy(other_opt)
    else
      ind_first = ind[0] - "a"[0]
      new_ind = ind[1..-1]

      other_opt_hash = Hash.new()
      if(other_opt)
        other_opt.opts.each {|opt|
          other_opt_hash[opt.rel_path] = opt
        }
      end
      pairs = []
      @opts.each {|opt|
        other_opt = other_opt_hash[opt.rel_path]
        if(other_opt)
          other_opt_hash.delete(other_opt.rel_path)
        end
        pairs.push [opt,other_opt,false]
      }
      other_opt_hash.each_pair {|key,value|
        pairs.push [value,nil,true]
      }
      pairs.sort! {|a,b|
        a[0].rel_path <=> b[0].rel_path
      }
      next_ind = 0
      pairs.each {|pair|
        if(ind_first == next_ind)
          if(new_ind == "")
            if(pair[2])
              opt = pair[0].clone()
              opt.change_path(@full_path)
              @opts.push opt
            else
              pair[0].copy(pair[1]) if(pair[1])
            end
          else
            if(pair[2])
              raise InvalidIndicatorError.new("indicator part #{new_ind} specifies non-existant copy to location")
            else
              if(pair[1])
                pair[0].copy_ind(pair[1],new_ind)
              end
            end
          end
        end
        next_ind += 1
      }
    end
  end
  def clone_copy(opt)
    super(opt)
    @name  = opt.name.clone()
    @opts = opt.opts.collect {|sub_opt|
      sub_opt.clone()
    }
  end
  def clone()
    opt = OptionGroup.new
    opt.clone_copy(self)
    opt
  end
  def change_path(path)
    super(path)
    @opts.each {|opt|
      opt.change_path(@full_path)
    }
  end
  def debug_field_string()
    super() + " name=" + @name
  end
  def debug_print()
    super()
    @opts.each {|opt|
      opt.debug_print()
    }
  end
  def pretty_print_string()
    @name
  end
  def pretty_print()
    super()
    @opts.each {|opt|
      opt.pretty_print()
    }
  end
  def diff(other_opt)
    return (super(other_opt) ||
            @name != other_opt.name)
  end
  def count_diffs(other_opt)
    diff_count = 0
    diff_count += 1 if diff(other_opt)

    other_opt_hash = Hash.new()
    if(other_opt)
      other_opt.opts.each {|opt|
        other_opt_hash[opt.rel_path] = opt
      }
    end
    @opts.each {|opt|
      other_opt = other_opt_hash[opt.rel_path]
      if(other_opt)
        other_opt_hash.delete(other_opt.rel_path)
      end
      diff_count += opt.count_diffs(other_opt)
    }
    other_opt_hash.each_pair {|key,value|
      diff_count += value.count_diffs(nil)
    }
    return diff_count
  end
  def print_pair(other_opt,indicator="a",reverse=false)
    super(other_opt,indicator,reverse)
    other_opt_hash = Hash.new()
    if(other_opt)
      other_opt.opts.each {|opt|
        other_opt_hash[opt.rel_path] = opt
      }
    end
    pairs = []
    @opts.each {|opt|
      other_opt = other_opt_hash[opt.rel_path]
      if(other_opt)
        other_opt_hash.delete(other_opt.rel_path)
      end
      pairs.push [opt,other_opt,reverse]
    }
    other_opt_hash.each_pair {|key,value|
      pairs.push [value,nil,true]
    }
    pairs.sort! {|a,b|
      a[0].rel_path <=> b[0].rel_path
    }
    next_ind = "a"
    pairs.each {|pair|
      pair[0].print_pair(pair[1],indicator+next_ind,pair[2])
      next_ind.succ!
    }
  end
  def write()
    @opts.each {|opt|
      opt.write()
    }
  end
end

class OptionConfigFile < OptionGroup
  attr_accessor :config_file # ConfigFile class for this option
  def fill(parent)
    print "filling option config file #{@full_path}\n" if($debug)
    @name = File.basename(@rel_path)

    @config_file = ConfigFile.new()
    @config_file.read(@full_path)

    fields = []
    @config_file.each() {|setting_elem|
      fields.push setting_elem
    }
    fields.sort! {|a,b| a.name <=> b.name}
    field_hash = Hash.new
    if(!@opt_skel.nil?)
      @opt_skel.opts.each {|opt_skel_sub|
        field_name_sub = OptionAtomField.split_path(opt_skel_pub.path)[1]
        field_hash[field_name_sub] = opt_skel_sub
      }
    end

    @opts = []
    fields.each {|field|
      match_opt_skel = field_hash[field.name]
      @opts.push Option.create(self,OptionAtomField.join_path(@full_path,field.name),nil,match_opt_skel)
    }
  end
  def clone_copy(opt)
    @config_file = opt.config_file.clone()
    super(opt)
    @opts.each {|sub_opt|
      sub_opt.set_config_file(@config_file)
    }
  end
  def clone()
    opt = OptionConfigFile.new
    opt.clone_copy(self)
    opt
  end
  def change_path(path)
    super(path)
    @config_file.filename=@full_path
  end
  def copy(other_opt)
    @config_file.copy(other_opt.config_file)
    other_opt_hash = Hash.new()
    other_opt.opts.each {|opt|
      other_opt_hash[opt.rel_path] = opt
    }
    @opts.each {|opt|
      if(other_opt_hash[opt.rel_path])
        other_opt_hash.delete(opt.rel_path)
        opt.set_config_file(@config_file)
      else
        @config_file.add_setting(opt.setting)
        opt.set_config_file(@config_file)
      end
    }
    other_opt_hash.each_pair {|key,value|
      @opts.push Option.create(self,OptionAtomField.join_path(@full_path,key),nil,value.opt_skel)
    }
  end
  def debug_field_string()
    super() + " config_file=" + @config_file.inspect
  end
  def write()
    @config_file.write()
  end
end

class OptionDir < OptionGroup
  def fill(parent)
    print "filling option dir #{@full_path}\n" if($debug)
    @name = @rel_path
    files = []
    Dir.foreach(@full_path) {|dir_elem|
      if(!ignore_file?(dir_elem))
        files.push dir_elem
      end
    }
    files.sort!
    file_hash = Hash.new
    if(!@opt_skel.nil?)
      @opt_skel.opts.each {|opt_skel_sub|
        file_hash[File.basename(opt_skel_sub.path)] = opt_skel_sub
      }
    end
    @opts = []
    files.each {|file|
      match_opt_skel = file_hash[File.basename(file)]
      @opts.push Option.create(self,@full_path,file,match_opt_skel)
    }
  end
  def copy(other_opt)
    other_opt_hash = Hash.new()
    other_opt.opts.each {|opt|
      other_opt_hash[opt.rel_path] = opt
    }
    opt_hash = Hash.new()
    opts.each {|opt|
      opt_hash[opt.rel_path] = opt
    }
    new_opt_keys = other_opt_hash.keys() - opt_hash.keys()
    @opts.each {|opt|
      if(other_opt_hash[opt.rel_path])
        opt.copy(other_opt_hash[opt.rel_path])
        other_opt_hash.delete(opt.rel_path)
      end
    }
    new_opt_keys.each {|new_opt_key|
      new_opt = other_opt_hash[new_opt_key]
      opt = new_opt.clone()
      opt.change_path(@full_path)
      @opts.push opt
    }
  end
  def clone()
    opt = OptionDir.new
    opt.clone_copy(self)
    opt
  end
end

################################################################################
# Menu stuff
################################################################################

class MenuItem
  attr_accessor :binding
  attr_accessor :text
  attr_accessor :function
  def initialize(text,function)
    @text     = text
    @function = function
    @binding = text.split(/[ )]/)[0]
    if(@binding.nil?)
      raise ArgumentError.new("invalid menu text has no binding '"+text+"'")
    end
  end
end

class Menu
  attr_reader :menu_items

  def initialize(menu_items)
    @menu_items = menu_items
  end

  def present()
    choice = nil
    choice_params = nil
    valid_choice = false
    valid_choices = @menu_items.collect {|menu_item| menu_item.binding}
    
    while(!valid_choice)
      print "###############################################\n";
      print "please enter command from the following options\n";
      @menu_items.each { |menu_item|
        print "#{menu_item.text}\n"
      }
      response = $stdin.gets
      response.chomp!()
      res_choice = nil
      res_params = nil
      if(response =~ /^(\S+)\s+(.*)$/)
        res_choice = $1
        res_params = $2
      else
        res_choice = response
      end
      print "res_choice '#{res_choice}' res_params '#{res_params}'\n" if($debug)
      valid_choices.each {|valid_choice_item|
        if(valid_choice_item == res_choice)
          valid_choice = true
          choice        = res_choice
          choice_params = res_params
        end
      }
    end
    
    print "choice was '#{choice}' with params '#{choice_params}'\n" if($debug)

    return [choice, choice_params]
  end
  
  def do()
    menu_text = @menu_items.collect {|menu_item|
      menu_item.text
    }
    menu_actions = @menu_items.collect {|menu_item|
      menu_item.function
    }
    
    choice = nil
    params = nil
    vals = present()
    choice = vals[0]
    params = vals[1]
    
    func = nil
    @menu_items.each {|menu_item|
      if(menu_item.binding == choice)
        func = menu_item.function
      end
    }
    
    param_list = params ? params.split(/ /) : []
    func.call(param_list)
    #$mp.send(func,param_list)
  end
end

def print_usage()
  print $usage
end

$help=false

opts = GetoptLong.new \
(["--help", "-h", GetoptLong::NO_ARGUMENT]);

opts.ordering = GetoptLong::RETURN_IN_ORDER;

begin
  opts.each {|opt,arg|
    case opt
    when "--help"
      $help=true
    end
  }
rescue => except
  if(opts.error?)
    print "#{opts.error_message}\n"
  else
    print "unknown exception caught\n"
    p except
  end
  print_usage()
  exit(2)
end

if(opts.error?)
  print_usage()
  exit(1)
end

if($help)
  print_usage()
  exit(0)
end

# returns option skeleton
def read_options_skel(file)
  path = File.dirname (file)
  file = File.basename(file)

  opt_skel = nil
  conf_opts_file = File.new(File.join(path,file))
  old_pos = conf_opts_file.pos
  while(opt_skel.nil? && line=conf_opts_file.gets())
    if(line =~ /option group "global"/)
      conf_opts_file.pos = old_pos
      opt_skel = OptionGroupSkel.read(conf_opts_file,0)
    end
    old_pos = conf_opts_file.pos
  end
  
  opt_skel
end

#def read_options(cur_path,opt_skel)
#  opt = nil
#
#  case opt_skel.type
#  when OptionConfig.File
#    opt = OptionFile.new (cur_path,opt_skel.path)
#  when OptionConfig.Field
#    opt = OptionField.new(cur_path,opt_skel.path,opt_skel.field_name)
#  when OptionConfig.Group
#    new_path = cur_path + opt_skel.path_rel
#    opts = opt_skel.opt_skels.collect {|sub_obt_cfg|
#      read_options(new_path,sub_opt_skel)
#    }
#    opt = OptionGroup.new(opt_skel.name,opts)
#  end
#
#  opt
#end
#
#def read_options_from_memstick(opt_skel)
#  memstick = "/memstick/"
#  
#  opts = OptionGroup.new
#  
#  vis_opts = OptionGroup.new("vision options")
#  opt_skel.each {|opt_skel|
#    opt_skel.opt("vis_opt_skels").each {|vis_opt_skel|
#      case vis_opt_skel.type
#      when OptionConfig.File
#        vis_opts.add_opt(OptionFile.new(memstick,vis_opt_skel.path))
#      when OptionConfig.Field
#        vis_opts.add_opt(OptionField.new(memstick,vis_opt_skel.path,vis_opt_skel.field_name))
#      end
#    }
#    
#  mot_opts = OptionGroup.new("motion options")
#  beh_opts = OptionGroup.new("behavior options")
#
#  opts
#end

#sub write_behave_cfg() {
#  my @behave_cfg_lines=();
#
#  open(BEHAVE_CFG,"</memstick/config/behave.cfg");
#  while($behave_cfg_line=<BEHAVE_CFG>) {
#    push @behave_cfg_lines,$behave_cfg_line;
#  }
#  close(BEHAVE_CFG);
#
#  for(my $i=0; $i<=$#behave_cfg_lines; $i++) {
#    $behave_cfg_lines[$i] =~ /^(.*teamColor\s*=\s*")[^"]*(".*)$/ && do { # " extra quote keeps emacs semi-happy
#      $behave_cfg_lines[$i] = "$1$jersey$2\n";
#    };
#    $behave_cfg_lines[$i] =~ /^(.*defendGoal\s*=\s*")[^"]*(".*)$/ && do { # " extra quote keeps emacs semi-happy
#      $behave_cfg_lines[$i] = "$1$goal_to_defend$2\n";
#    };
#    $behave_cfg_lines[$i] =~ /^(.*role\s*=\s*")[^"]*(".*)$/ && do { # " extra quote keeps emacs semi-happy
#      $behave_cfg_lines[$i] = "$1$role$2\n";
#    };
#    $behave_cfg_lines[$i] =~ /^(.*fieldSide\s*=\s*")[^"]*(".*)$/ && do { # " extra quote keeps emacs semi-happy
#      $behave_cfg_lines[$i] = "$1$side$2\n";
#    };
#  }
#
#  open(BEHAVE_CFG,">/memstick/config/behave.cfg");
#  for(my $i=0; $i<=$#behave_cfg_lines; $i++) {
#    print BEHAVE_CFG $behave_cfg_lines[$i];
#  }
#  close(BEHAVE_CFG);
#}
#
#sub write_thresholds() {
#  if($thresholds eq "compA") {
#    system("cp /usr0/slenser/projects/dogs/dog_images_2001/seattle/conf/compA/08-03/thresh.tm /memstick/config/thresh.tm");
#  } elsif($thresholds eq "practice") {
#    system("cp /usr0/slenser/projects/dogs/dog_images_2001/seattle/conf/practice/08-02/thresh.tm /memstick/config/thresh.tm");
#  } elsif($thresholds eq "lab") {
#    system("cp /usr0/slenser/projects/dogs/dog_images_2001/lab/09-04/thresh.tm /memstick/config/thresh.tm");
#  } elsif($thresholds eq "unknown") {
#    print "thresholds unknown so leaving untouched\n";
#  } else {
#    print "ATTEMPT TO WRITE INVALID THRESHOLDS '$thresholds'\n";
#  }
#}
#
#sub write_camera_gain() {
#  if($camera_gain eq "compA") {
#    system("cp /usr0/slenser/projects/dogs/code01r/agent/config/camera.cfg /memstick/config/camera.cfg");
#  } elsif($camera_gain eq "practice") {
#    system("cp /usr0/slenser/projects/dogs/code01r/agent/config/camera_practice.cfg /memstick/config/camera.cfg");
#  } elsif($camera_gain eq "lab") {
#    system("cp /usr0/slenser/projects/dogs/code01/agent/config/camera.cfg /memstick/config/camera.cfg");
#  } elsif($camera_gain eq "unknown") {
#    print "camera gain unknown so leaving untouched\n";
#  } else {
#    print "ATTEMPT TO WRITE INVALID CAMERA GAINS '$camera_gain'\n";
#  }
#}
#
#sub write_cur_params() {
#  print "WRITING THIS CONFIGURATION TO MEMORY STICK!\n";
#  &print_cur_params();
#
#  &write_behave_cfg();
#  &write_thresholds();
#  &write_camera_gain();
#
#  print "WRITE COMPLETE.\n";
#  $changed = 0;
#}
#
#sub read_behave_cfg() {
#  my @behave_cfg_lines=();
#
#  open(BEHAVE_CFG,"</memstick/config/behave.cfg");
#  while($behave_cfg_line=<BEHAVE_CFG>) {
#    push @behave_cfg_lines,$behave_cfg_line;
#  }
#  close(BEHAVE_CFG);
#
#  for(my $i=0; $i<=$#behave_cfg_lines; $i++) {
#    $behave_cfg_lines[$i] =~ /^(.*teamColor\s*=\s*")([^"]*)(".*)$/ && do { # " keep emacs semi-happy
#      $jersey = $2;
#    };
#    $behave_cfg_lines[$i] =~ /^(.*defendGoal\s*=\s*")([^"]*)(".*)$/ && do { # " keep emacs semi-happy
#      $goal_to_defend = $2;
#    };
#    $behave_cfg_lines[$i] =~ /^(.*role\s*=\s*")([^"]*)(".*)$/ && do { # " keep emacs semi-happy
#      $role = $2;
#    };
#    $behave_cfg_lines[$i] =~ /^(.*fieldSide\s*=\s*")([^"]*)(".*)$/ && do { # " keep emacs semi-happy
#      $side = $2;
#    };
#  }
#}
#
#sub read_thresholds() {
#  my $ret_val;
#
#  $thresholds = "unknown";
#
#  $ret_val = system("diff -q /usr0/slenser/projects/dogs/dog_images_2001/seattle/conf/compA/08-03/thresh.tm /memstick/config/thresh.tm >/dev/null");
#  $ret_val /= 256;
#  if($ret_val == 0) {
#    $thresholds = "compA";
#  }
#
#  $ret_val = system("diff -q /usr0/slenser/projects/dogs/dog_images_2001/seattle/conf/practice/08-02/thresh.tm /memstick/config/thresh.tm >/dev/null");
#  $ret_val /= 256;
#  if($ret_val == 0) {
#    $thresholds = "practice";
#  }
#
#  $ret_val = system("diff -q /usr0/slenser/projects/dogs/dog_images_2001/lab/09-04/thresh.tm /memstick/config/thresh.tm >/dev/null");
#  $ret_val /= 256;
#  if($ret_val == 0) {
#    $thresholds = "lab";
#  }
#}
#
#sub read_camera_gain() {
#  my $camera_cfg_line;
#  my $camera_compA=1;
#  my $camera_practice=1;
#  my $camera_lab=1;
#
#  open(CAMERA_CFG,"</memstick/config/camera.cfg");
#  while($camera_cfg_line=<CAMERA_CFG>) {
#    print "camcfg $camera_cfg_line";
#    $camera_cfg_line =~ /^(.*whiteBalance\s*=\s*)([a-zA-Z]*)/ && do {
#      my $wb=$2;
#      print "wb '$wb'\n";
#      $camera_lab=0      if($wb ne "indoor"     );
#      $camera_compA=0    if($wb ne "flourescent");
#      $camera_practice=0 if($wb ne "flourescent");
#    };
#    $camera_cfg_line =~ /^(.*gain\s*=\s*)([a-zA-Z]*)/ && do {
#      my $gain=$2;
#      print "gain '$gain'\n";
#      $camera_lab=0      if($gain ne "mid");
#      $camera_compA=0    if($gain ne "low");
#      $camera_practice=0 if($gain ne "low");
#    };
#    $camera_cfg_line =~ /^(.*shutterSpeed\s*=\s*)([a-zA-Z]*)/ && do {
#      my $shutter=$2;
#      print "shutter '$shutter'\n";
#      $camera_lab=0      if($shutter ne "mid");
#      $camera_compA=0    if($shutter ne "fast");
#      $camera_practice=0 if($shutter ne "mid" );
#    };
#    print "compA $camera_compA practice $camera_practice\n";
#  }
#  close(CAMERA_CFG);
#
#  if($camera_practice) {
#    $camera_gain="practice";
#  } elsif($camera_compA) {
#    $camera_gain="compA";
#  } elsif($camera_lab) {
#    $camera_gain="lab";
#  } else {
#    $camera_gain="unknown";
#  }
#}
#
#sub read_cur_params() {
#  &read_behave_cfg();
#  &read_thresholds();
#  &read_camera_gain();
#}

set_jersey = proc{|params|
  if(params[0] == "red")
    $jersey         = "red"
    $goal_to_defend = "yellow"
    $changed = 1
  elsif(params[0] == "blue")
    $jersey         = "blue"
    $goal_to_defend = "cyan"
    $changed = 1
  else
    print "invalid jersey color '#{params[0]}'\n"
  end
}

#sub set_thresholds() {
#  my $param_str = shift @_;
#  my @params = split(/ /,$param_str);
#
#  if($params[0] eq "compA") {
#    $thresholds  = "compA";
#    $camera_gain = "compA";
#    $changed = 1;
#  } elsif($params[0] eq "practice") {
#    $thresholds  = "practice";
#    $camera_gain = "practice";
#    $changed = 1;
#  } elsif($params[0] eq "lab") {
#    $thresholds  = "lab";
#    $camera_gain = "lab";
#    $changed = 1;
#  } else {
#    print "invalid thresholds '$params[0]'\n";
#  }
#}

set_role = proc {|params|
  if(params[0] == "attack")
    $role = "attack";
    $changed = 1;
  elsif(params[0] == "goalie")
    $role = "goalie";
    $changed = 1;
  else
    print "invalid role '#{params[0]}'\n";
  end
}

set_side = proc {|params|
  if(params[0] == "pos")
    $side = "pos";
    $changed = 1;
  elsif(params[0] == "neg")
    $side = "neg";
    $changed = 1;
  else
    print "invalid side '#{params[0]}'\n";
  end
}

quit = proc{|args|
  override = false

  if($changed)
    print "*************************************************\n"
    print "* Warning: you have not saved these settings!!! *\n"
    print "*************************************************\n"
    
    print "proceed anyway (y/n)\n"
    val=nil
    begin
      val=$stdin.gets;
    end while(val !~ /^y/ && val !~ /^n/);
    if(val =~ /^y/)
      override=true;
    end
  end

  if(!$changed ||
     ($changed && override))
    exit(0)
  end
}

#def noop_func(args)
#  printf "noop_func called with '#{args.join(',')}'\n"
#end

noop_func = proc {|args|
  printf "noop_func called with '#{rest.join(',')}'\n"
}

read_skel_test = proc{|args|
  opt_skel = read_options_skel(ENV["DOGROOT"]+"/agent/config/config.opts")
  print "####### option skeletion\n"
  p opt_skel
  opt = Option.create(nil,".",nil,opt_skel)
  print "####### option structure\n"
  opt.debug_print()
}

$opt_skel = nil
$opt_skel = read_options_skel(ENV["DOGROOT"]+"/agent/config/config.opts")

$cur_path = "."
$cur_opt = nil
$alt_path = "."
$alt_opt = nil

set_path = proc {|args|
  path = "./"
  if(args[0])
    path = args[0]
  end
  $cur_path = path
  $cur_opt.change_path($cur_path)
}

load_configuration = proc {|args|
  path = "."
  if(args[0])
    path = args[0]
  end
  $cur_path = path
  begin
    $cur_opt = Option.create(nil,$cur_path,nil,$opt_skel)
  rescue Exception => except
    print "Problem reading configuration from #{$cur_path}\n"
    print except,"\n"
    $cur_opt = nil
    $cur_path = "."
  end
}

load_alt_configuration = proc {|args|
  path = "."
  if(args[0])
    path = args[0]
  end
  $alt_path = path
  begin
    $alt_opt = Option.create(nil,$alt_path,nil,$opt_skel)
  rescue Exception => except
    print "Problem reading configuration from #{$alt_path}\n"
    print except,"\n"
    $alt_opt = nil
    $alt_path = "."
  end
}

write_configuration = proc {|args|
  $cur_opt.write()
}

copy_configuration = proc {|args|
  ind = ""
  if(args[0])
    ind = args[0]
  end
  if(ind == "")
    $cur_opt.copy($alt_opt)
  else
    begin
      $cur_opt.copy_ind($alt_opt,ind)
    rescue InvalidIndicatorError =>except
      print "bad indicator:"+except.to_s()+"\n"
    end
  end
}

$main_menu =
  Menu.new(
           [ MenuItem.new("cd [<path>]) change the <path> the curent configuration [defaults to .]",         set_path),
             MenuItem.new("a  [<path>]) load alternate configuration from <path>   [defaults to .]",         load_alt_configuration),
             MenuItem.new("l  [<path>]) load configuration from path               [defaults to last path]", load_configuration),
             MenuItem.new("w          ) write configuration to current path",                                write_configuration),
             MenuItem.new("cp [<ind>] ) copy the part of alt conf indicated by ind to the current conf [defaults to \"\"]", copy_configuration),
             MenuItem.new("q          ) quit/exit",                                                          quit      ),
             MenuItem.new("x          ) quit/exit",                                                          quit      ),
           ])

$cur_menu = $main_menu

$changed = false

def print_cur_params()
  print "\n"
  if($cur_opt.nil?)
    print "no current configuration loaded\n"
  else
    $cur_opt.debug_print()
    #$cur_opt.pretty_print()
    printf("%-#{$ind_width.to_s}s %#{$diff_width.to_s}s %-#{$side_width.to_s}s %-#{$side_width.to_s}s\n",
           "ind","df",$cur_path,($alt_opt ? $alt_path : ""))
    $cur_opt.print_pair($alt_opt,"",false)
  end
  print "\n"
end

#&read_cur_params()
begin
  while(true)
    print_cur_params()
    $cur_menu.do()
  end
rescue Exception => e
  p e
  $@.each{|path|
    print "#{path}\n"
  }
end 
