=begin header
Simple scheme for properties

    $Author: Hiroshi IGARASHI <igarashi@ueda.info.waseda.ac.jp>$
    $Date: Mon Mar  8 16:36:13 1999$
=end

class Properties
=begin
Class Properties
  This class is *not* Thread-Safe.
=end

  include Enumerable

  attr_accessor(:default)
=begin
Properties object for dafault values.
=end
  # @changed
  # whether is changed
  # @property
  # mapping key to value
  # @desc
  # description lines
  # @index
  # mapping key -> (line number in @desc)

  def initialize(default=nil)
=begin
Make an empty Properties with specified default values.
=end
    @default = default
    @changed = true
    @property = {}
    @desc = []
    @index = {}
  end

  def setProperty(key, val)
=begin
Sets a value <var>val</var> to property
which has the specified name <var>key</var>.
Returns the previous value,
or <code>nil</code> when the property has no value.
=end
    p([key, val]) if $DEBUG
    @changed = true
    old_val = @property[key]
    @property[key] = val
    # remove description line if val is nil.
    if val.nil?
      @desc[@index[key]] = nil
    end
    old_val
  end

  alias []= setProperty
=begin
operator form of setProperty
=end

  def getProperty(key, default_val=nil)
=begin
Returns the value of the property specified by <var>key</var>.
When there is not the property,
returns the default value <var>default</var> if it is specified,
returns <code>nil</code> otherwise.
=end
    p([key, default_val]) if $DEBUG
    if val = @property[key]
      val
    else
      if (not @default.nil?) and
	  (val = @default.getProperty(key))
	val
      else
	default_val
      end
    end
  end

  alias [] getProperty
=begin
oprerator form of getProperty
=end

  def method_missing(mid, *args, &iter)
    p([mid.id2name, args]) if $DEBUG
    case mid.id2name
    when /^(.+)=$/
      key = $1 
      val = args[0]
      self.setProperty(key, val)
    when /^(.+)$/
      key = $1
      default_val = args[0]
      self.getProperty(key, default_val)
    else
      raise NameError.new
    end
  end

  def addComment(comment)
=begin
Adds a comment to the tail of the descriptions
=end
    # @desc << ('; ' + comment)
    @desc << comment
    self
  end

  def list(output=STDERR)
=begin
Lists the contents of this properties
to specified stream <var>output</var>.
=end
    output.puts("changed = " + @changed.inspect)
    @property.each_pair do |key, value|
      # output.lprintln(key, ' = ', value)
      output.print(key, ' = ', value.inspect, "\n")
    end
    nil
  end
  alias dump list

  def load(input)
=begin
Loads properties from <var>input</var>.
<var>input</var> must be IO or String(filename).
=end
    case input
    # input stream
    when IO
      loadStream(input)
    # file name
    when String
      input = File.expand_path(input)
      file = File.open(input)
      loadStream(file)
    else
      raise 'Properties:invalid input'
    end
    self
  end
  public(:load)

  private
  def loadStream(str)
=begin
Loads descriptions from the stream <var>str</var>.
=end
    @changed = false
    @desc = []
    @index = {}
    line_num = 0
    while line = str.gets
      line.strip!
      # line = Kconv.tolocal(line)
      @desc[line_num] = line
      if line == ''
	# empty line -> nop
      elsif line =~ /^\s*;.*$/
	# comment -> nop
      elsif line =~ /^\s*#.*$/
	# comment -> nop
      elsif line =~ /^\s*([^\s=]+)\s*=\s*(.+)\s*$/
	key, val = $1, eval($2)
	# p [key, val]
	@property[key] = val
	@index[key] = line_num
      else
	# error (should some exception be raised?)
	STDERR.print("Properties:invalid description:#{$.}\n")
      end
      line_num += 1
    end
  end

  public
  def save(output)
=begin
Saves properties to <var>outptu</var>.
<var>output</var> must be IO or String(filename).
=end
    case output
    # output stream
    when IO
      updateDesc
      saveStream(output)
    # filename
    when String
      updateDesc
      output = File.expand_path(output)
      file = File.open(output, 'w')
      saveStream(file)
      file.close
    else
      p output.type
      raise 'Properties:invalid output'
    end
    self
  end

  def each(*args, &iter)
    @property.each(*args, &iter)
  end

  def each_key(*args, &iter)
    @property.each_key(*args, &iter)
  end

  def each_pair(*args, &iter)
    @property.each_pair(*args, &iter)
  end

  private
  def updateDesc
=begin
Reflects current properties to descriptions.
=end
    if @changed
      @property.each_pair do |key, val|
	# key is new property
	if @index[key].nil?
	  @index[key] = @desc.length
	end
	# update the description line.
	@desc[@index[key]] = "#{key} = #{val.inspect}"
      end
    end
    self
  end

  def saveStream(str)
=begin
Saves properties to output stream <var>str</var>.
=end
    @desc.each do |line|
      # str.lprintln(line) unless line.nil?
      str.print(line, "\n") unless line.nil?
    end
  end
end
