# dbus/type.rb - module containing low-level D-Bus data type information
#
# This file is part of the ruby-dbus project
# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License, version 2.1 as published by the Free Software Foundation.
# See the file "COPYING" for the exact licensing terms.

module DBus

# = D-Bus type module
#
# This module containts the constants of the types specified in the D-Bus
# protocol.
module Type
  # Mapping from type number to name and alignment.
  TypeMapping = {
    0 => ["INVALID", nil],
    ?y => ["BYTE", 1],
    ?b => ["BOOLEAN", 4],
    ?n => ["INT16", 2],
    ?q => ["UINT16", 2],
    ?i => ["INT32", 4],
    ?u => ["UINT32", 4],
    ?x => ["INT64", 8],
    ?t => ["UINT64", 8],
    ?d => ["DOUBLE", 8],
    ?r => ["STRUCT", 8],
    ?a => ["ARRAY", 4],
    ?v => ["VARIANT", 1],
    ?o => ["OBJECT_PATH", 4],
    ?s => ["STRING", 4],
    ?g => ["SIGNATURE", 1],
    ?e => ["DICT_ENTRY", 8],
    ?h => ["UNIX_FD", 4],
  }
  # Defines the set of constants
  TypeMapping.each_pair do |key, value|
    Type.const_set(value.first, key)
  end

  # Exception raised when an unknown/incorrect type is encountered.
  class SignatureException < Exception
  end

  # = D-Bus type conversion class
  #
  # Helper class for representing a D-Bus type.
  class Type
    # Returns the signature type number.
    attr_reader :sigtype
    # Return contained member types.
    attr_reader :members

    # Create a new type instance for type number _sigtype_.
    def initialize(sigtype)
      if not TypeMapping.keys.member?(sigtype)
        raise SignatureException, "Unknown key in signature: #{sigtype.chr}"
      end
      @sigtype = sigtype
      @members = Array.new
    end

    # Return the required alignment for the type.
    def alignment
      TypeMapping[@sigtype].last
    end

    # Return a string representation of the type according to the
    # D-Bus specification.
    def to_s
      case @sigtype
      when STRUCT
        "(" + @members.collect { |t| t.to_s }.join + ")"
      when ARRAY
        "a" + child.to_s
      when DICT_ENTRY
        "{" + @members.collect { |t| t.to_s }.join + "}"
      else
        if not TypeMapping.keys.member?(@sigtype)
          raise NotImplementedError
        end
        @sigtype.chr
      end
    end

    # Add a new member type _a_.
    def <<(a)
      if not [STRUCT, ARRAY, DICT_ENTRY].member?(@sigtype)
        raise SignatureException
      end
      raise SignatureException if @sigtype == ARRAY and @members.size > 0
      if @sigtype == DICT_ENTRY
        if @members.size == 2
          raise SignatureException, "Dict entries have exactly two members"
        end
        if @members.size == 0
          if [STRUCT, ARRAY, DICT_ENTRY].member?(a.sigtype)
            raise SignatureException, "Dict entry keys must be basic types"
          end
        end
      end
      @members << a
    end

    # Return the first contained member type.
    def child
      @members[0]
    end

    def inspect
      s = TypeMapping[@sigtype].first
      if [STRUCT, ARRAY].member?(@sigtype)
        s += ": " + @members.inspect
      end
      s
    end
  end # class Type

  # = D-Bus type parser class
  #
  # Helper class to parse a type signature in the protocol.
  class Parser
    # Create a new parser for the given _signature_.
    def initialize(signature)
      @signature = signature
      @idx = 0
    end

    # Returns the next character from the signature.
    def nextchar
      c = @signature[@idx]
      @idx += 1
      c
    end

    # Parse one character _c_ of the signature.
    def parse_one(c)
      res = nil
      case c
      when ?a
        res = Type.new(ARRAY)
        c = nextchar
        raise SignatureException, "Parse error in #{@signature}" if c == nil
        child = parse_one(c)
        res << child
      when ?(
        res = Type.new(STRUCT)
        while (c = nextchar) != nil and c != ?)
          res << parse_one(c)
        end
        raise SignatureException, "Parse error in #{@signature}" if c == nil
      when ?{
        res = Type.new(DICT_ENTRY)
        while (c = nextchar) != nil and c != ?}
          res << parse_one(c)
        end
        raise SignatureException, "Parse error in #{@signature}" if c == nil
      else
        res = Type.new(c)
      end
      res
    end

    # Parse the entire signature, return a DBus::Type object.
    def parse
      @idx = 0
      ret = Array.new
      while (c = nextchar)
        ret << parse_one(c)
      end
      ret
    end
  end # class Parser
end # module Type

# shortcuts

# Parse a String to a DBus::Type::Type
def type(string_type)
  Type::Parser.new(string_type).parse[0]
end
module_function :type

# Make an explicit [Type, value] pair
def variant(string_type, value)
  [type(string_type), value]
end
module_function :variant

end # module DBus
