require_relative "collector"
require_relative "binary_writer"

module Risc

  # A RiscCollection is the last virtual stop on the way to binary.
  # It creates a Linker to go to binary. The name linker came in analogy to
  # "normal" (ie c world) lingo, because there too the linker is all about
  # positioning code and data.
  #
  # Here we also do the assembling, which (at least in arm) creates this mess
  # of dependencies. As a simple example imagine a branch, a load and the label
  # to which we jump. The brnahc needs the address, but the load may be 4 or 8
  # instructions, and thus the label address is only known after the load.
  # Exrapolate times 10, that's why this works with listeners and a sort
  # of self organizing aproach (see class Position).

  class Linker
    include Util::Logging
    log_level :info

    def initialize(platform , assemblers , constants)
      if(platform.is_a?(Symbol))
        platform = platform.to_s.capitalize
        platform = Risc::Platform.for(platform)
      end
      raise "Platform must be platform, not #{platform.class}" unless platform.is_a?(Platform)
      @platform = platform
      @assemblers = assemblers
      @constants = constants
      @cpu_init = cpu_init_init
    end

    attr_reader  :constants , :cpu_init
    attr_reader  :platform , :assemblers

    # machine keeps a list of all objects and their positions.
    # this is lazily created with a collector
    def object_positions
      Collector.collect_space(self) if Position.positions.length < 2 #one is the label
      Position.positions
    end

    # To create binaries, objects (and labels) need to have a position
    # (so objects can be loaded and branches know where to jump)
    #
    # Position in the order
    # - initial jump
    # - all objects
    # - all code (BinaryCode objects)
    # As code length may change during assembly, this way at least the objects stay
    # in place and we don't have to deal with changing loading code
    def position_all
      #need the initial jump at 0 and then functions
      Position.new(@cpu_init).set(0)
      code_start = position_objects( @platform.padding )
      # and then everything code
      position_code(code_start)
    end

    # go through everything that is not code (BinaryCode) and set position
    # padded_length is what determines an objects (byte) length
    # return final position that is stored in code_start
    def position_objects(at)
      # want to have the objects first in the executable
      sorted = object_positions.keys.sort do |left,right|
        left.class.name <=> right.class.name
      end
      previous = nil
      sorted.each do |objekt|
        next unless Position.is_object(objekt)
        before = at
        raise objekt.class unless( Position.set?(objekt)) #debug check
        position = Position.get(objekt).set(at)
        previous.position_listener(objekt) if previous
        previous = position
        at += objekt.padded_length
        log.debug "Object #{objekt.class}:0x#{before.to_s(16)} len: #{(at - before).to_s(16)}"
      end
      at
    end

    # Position all BinaryCode.
    #
    # So that all code from one method is layed out linearly (for debugging)
    # we go through methods, and then through all codes from the method
    #
    # start at code_start.
    def position_code(code_start)
      assemblers.each do |asm|
        Position.log.debug "Method start #{code_start.to_s(16)} #{asm.callable.name}"
        code_pos = CodeListener.init(asm.callable.binary, platform)
        instructions = asm.instructions
        InstructionListener.init( instructions, asm.callable.binary)
        code_pos.position_listener( LabelListener.new(instructions))
        code_pos.set(code_start)
        code_start = Position.get(asm.callable.binary.last_code).next_slot
      end
    end

    # Create Binary code for all methods and the initial jump
    # BinaryWriter handles the writing from instructions into BinaryCode objects
    #
    def create_binary
      prerun
      assemble
      log.debug "BinaryInit #{@cpu_init.object_id.to_s(16)}"
    end

    def prerun
      assemblers.each do |asm|
        asm.instructions.each do |ins|
          ins.precheck
        end
      end
    end

    def assemble
      assemblers.each do |asm|
        writer = BinaryWriter.new(asm.callable.binary)
        writer.assemble(asm.instructions)
      end
    end

    private
    # cpu_init come from translating the risc_init
    # risc_init is a branch to the __init__ method
    #
    def cpu_init_init
      init = assemblers.find {|asm| asm.callable.name == :__init__}
      risc_init = Branch.new( "__initial_branch__" , init.callable.binary )
      @platform.translator.translate(risc_init)
    end
  end
end