module Sbuilder

  class SymbolTable

    # ------------------------------------------------------------------    
    # Attributes

    # @attr  [Hash] types of :name -> :desc mappingssymbol names to add
    # @option types [String] :name of symbol to define
    # @option types [String] :desc description of symbol to define
    attr_reader :types

    alias_method :metatypes, :types

    # @attr [Hash] table of symbols
    # @option table [String] key is type name
    # @option table [Hash] spec-namesvalue spec names of 'type'
    # @option spec-names [String] key spec name of symbol entry
    # @option spec-names [String] value app name of symbol entry
    attr_reader  :table

    # ------------------------------------------------------------------
    # Logger
    
    PROGNAME = nil                            # progname for logger default class name
    include Sbuilder::Utils::MyLogger         # mix logger
    

    # ------------------------------------------------------------------
    # @!group Construct & configure

    def initialize( options = {} )
      @logger = getLogger( PROGNAME, options )
      @logger.info( "#{__method__} initialized" )

      @types = {}
      @table = {}
    end
    
    # @!endgroup

    # ------------------------------------------------------------------
    # @!group Build symbol table

    # Add type names to symbol table so that we can issue a warning if
    # unknon symbol types are entered
    #
    # @param  [Hash] symbol name to add
    # @option symbol [String] :name of symbol to define
    # @option symbol [String] :desc description of symbol to define
    #
    #
    def defineMetaType( symbol )
      @logger.info "#{__method__}: #{symbol}"

      # symbol[:name ]  = symbol[:name ]
      symbol[:prefix] = symbol[:prefix] || generatePrefix()
      
      types[symbol[:name]] = symbol
      
      # Initialize empty hash to store appName->specName mappings
      table[symbol[:name]] = table[symbol[:name]] || {}
    end

    # Create unique prefix entry.
    #
    # Implementation iteraterates '@types' in symbol table, for
    # entries with :prefix symbol starting with fixed characted 'x'.
    #
    # Take max of
    # 
    # 
    private def generatePrefix
      current_prefix=0
      @types.each do |k,v|
        if !v[:prefix].nil? && v[:prefix].start_with?( 'x' ) then
          result,tmp_prefix =  /x(\d+)/.match( v[:prefix]).to_a.flatten
          current_prefix = tmp_prefix.to_i > current_prefix ? tmp_prefix.to_i : current_prefix
        end
      end
      "x#{current_prefix+1}"
    end


    # Add 'spec' symbol of 'type' for 'app'. Issue a warning if 'type'
    # is undefined, or if 'type', 'spec' is redefined.
    #
    # @param [String] metatype of previsously defined metatype
    #
    # 
    # @param [String] spec name in specifcation domain
    #
    # @param [String] app name in application domain where 'spec' name
    # is associated
    
    def addSymbol( metatype, app, spec )

      # ------------------------------------------------------------------
      # Unknown metatype

      if types[metatype].nil? then
        typeDesc = "Unknown metatype '#{metatype}' encountered when mapping #{app} -> #{spec}"
        
        msg= <<-EOS
        #{typeDesc}
        
        use #{self.class.name}#defineMetaType(symbol) -method to define metatypes before adding
        symbols using  #{self.class.name}#addSymbol -method.

        EOS
        # warn msg
        @logger.error "#{__method__}: #{msg}"
        raise SymbolTableException.new msg                
      end
      
      # Init  hash to map appName -> specName
      table[metatype] = table[metatype] || {}

      # ------------------------------------------------------------------
      # Check for alias definition for specName

      # turn table outsize in:  specName -> [metatype,appName]
      specName2metatype_appName = table.inject( {} ) {|memo,(m,ah)|
        ah.each { |a,s|
          memo[s] = [m,a]
        }
        memo
      }

      # ------------------------------------------------------------------
      # error on aliased definition
      # if specName2metatype_appName[spec] && specName2metatype_appName[spec] != [metatype,app]
      #   msg = specNameAliasedMessage( metatype, app, *specName2metatype_appName[spec], spec )
      #   @logger.error "#{__method__} #{msg}"
      #   raise SymbolTableException.new msg
      # end

      # ------------------------------------------------------------------
      # Warn on duplicate definition
      if table[metatype][app] && table[metatype][app] != spec
        msg = specNameRerefined( metatype, app, table[metatype][app], spec )
        @logger.error "#{__method__}: #{msg}"
        raise SymbolTableException.new msg        
      end
      

      # ------------------------------------------------------------------
      # Warn on duplicate definition
      if ! table[metatype][app].nil?
        msg = "Duplicate symbol definition #{metatype}:#{spec}" 
        warn msg
        @logger.warn "#{__method__}: #{msg}"
      end

      # ------------------------------------------------------------------
      # Here normal add
      @logger.info "#{__method__} added metatype '#{metatype}', name in spec-domain: '#{spec}',  name in app domain: '#{app}'"
      
      table[metatype][app] = spec
    end
    
    # @!endgroup
    
    # ------------------------------------------------------------------
    # @!group Use symbol table

    # @return [String] definition of metatype
    def getMetaTypeDescription( metatype )
      getMetaTypeDefinition( metatype )[:desc]
    end

    # @return [String] definition of metatype, nil if not found
    def getMetaTypeDefinition( metatype )
      ret = types[metatype]

      if ret.nil?
        msg= <<-EOS
        Metatype definition for metatype '#{metatype}' not found.

        Known metatypes are: #{types.keys.join( ', ')}

        EOS
        # warn msg
        @logger.error "#{__method__}: #{msg}"
        raise SymbolTableException.new msg                

      end
      ret

    end

    # # @return [Hash] application-names for 'type'
    # def []( type )
    #   table[type]
    # end

    # Return 'spec' -name identified by 'app'/'type' combination
    #
    # @param [String] app name in application domain where 'spec' name
    # is associated
    #
    # @param [String] type of previsously defined type
    #
    def lookupSymbol( appName, metatype  )

      # Find specname (raise exceptio if metatype not found )
      specName =  getMetaTypeHash( metatype, appName )[appName]

      # Valid spec-name found
      if specName.nil? then
        msg=<<-EOS
        Unknown name '#{appName}' in metatype '#{metatype}'

        Valid app-names in meta-type '#{metatype}' are: #{table[metatype].keys.join(', ')}
        EOS
        @logger.error msg
        raise SymbolTableException.new msg
        
      end
      specName
        
    end

    # @!endgroup

    # @return [Hash] symbol map for 'metatype'
    private def  getMetaTypeHash( metatype, appName )
      
      # Valid 'metatype'
      if table[metatype].nil? then
        msg=<<-EOS
        Unknown meta-type '#{metatype}', when looking up for specification name for appName #{appName}

        Valid meta-types are: #{table.keys.join(', ')}
        EOS
        @logger.error msg
        raise SymbolTableException.new msg
      end

      # return hash for metape
      table[metatype]

    end

    # # @return [Hash] unknown metatype hash
    # private def unknownMetatype( metatype )
    #   {
    #     :name => metatype,
    #     :desc  => "Unknown metatype '#{metatype}'",
    #   }
    # end


    # @return [String] error message for aliasins specName
    private def specNameAliasedMessage( metatype1, appName1, metatype2, appName2, specName )
      <<-EOS
      Name '#{specName}' in specification code domain is defined twice
 
      Application name '#{appName1}' in metatype '#{metatype1}' is defined as '#{specName}'
      Application name '#{appName2}' in metatype '#{metatype2}' is defined as '#{specName}'
      EOS
    end

    # @return [String] error message for redfining metatype/appName
    private def specNameRerefined( metatype, appName, specName1, specName2 )
      <<-EOS
      Application name '#{appName}' in metatype '#{metatype}' refined

      specName1 = #{specName1}
      specName2 = #{specName1}
      EOS
    end
    
    
    
  end # class SymboTable

end # module