require 'asm/nodes'
require 'asm/block'
require 'stream_reader'
require 'stringio'
require "asm/string_literal"

module Asm
  
  # Program is the the top-level of the code hierachy, except it is not derived from code
  # instead a Program is a list of blocks (and string constants)
  
  # All code is created in blocks (see there) and there are two styles for that, for forward of backward
  # referencing. Read function block and add_block and Block.set
  
  
  class Program

    def initialize
      @blocks = []
      @string_table = {}
    end

    attr_reader  :blocks 

    # Assembling to string will return a binary string of the whole program, ie all blocks and the 
    # strings they use
    # As a memory reference this would be callable, but more likely you will hand it over to 
    # an ObjectWriter as the .text section and then link it. And then execute it :-)
    def assemble_to_string
      #put the strings at the end of the assembled code.
      # adding them will fix their position and make them assemble after
      @string_table.values.each do |data|
        add_block data
      end
      io = StringIO.new
      assemble(io)
      io.string
    end

    # Add a string to the string table. Strings are global and constant. So only one copy of each 
    # string exists
    # Internally StringLiterals are created and stored and during assembly written after the blocks
    def add_string str
      code = @string_table[str]
      return code if code
      data = Asm::StringLiteral.new(str)
      @string_table[str] = data
    end

    # Length of all blocks. Does not take strings into account as they are added after all blocks.
    # This is used to determine where a block when it is added after creation (see add_block)
    def length
      @blocks.inject(0) {| sum  , item | sum + item.length}
    end

    # This is how you add a forward declared block. This is called automatically when you 
    # call block with ruby block, but has to be done manually if not
    def add_block block
      block.at self.length
      @blocks << block
    end

    # return the block of the given name
    # or raise an exception, as this is meant to be called when the block is available 
    def get_block name
      block = @blocks.find {|b| b.name == name}
      raise "No block found for #{name} (in #{blocks.collect{|b|b.name}.join(':')})" unless block
      block
    end
    # this is used to create blocks. 
    # All functions that have no args are interpreted as block names
    # and if a block is provided, it is evaluated in the (ruby)blocks scope and the block added to the 
    # program immediately. 
    # If no block is provided (forward declaration), you must call code on it later 
    def method_missing(meth, *args, &block)
      if args.length == 0
        code = Block.new(meth.to_s , self )
        if block_given?
          add_block code
          code.instance_eval(&block)
        end
        return code
      else
        super
      end
    end
    

    private
    
    def assemble(io)
      @blocks.each do |obj|
        obj.assemble io
      end
    end
  end
end