require_relative "code"
module Vm

  # Because the idea of what one instruction does, does not always map one to one to real machine
  # instructions, and instruction may link to another instruction thus creating an arbitrary list
  # to get the job (the original instruciton) done
  
  # Admittately it would be simpler just to create the (abstract) instructions and let the machine 
  # encode them into what-ever is neccessary, but this approach leaves more possibility to 
  # optimize the actual instruction stream (not just the salama instruction stream). Makes sense?
  
  # We have basic classes (literally) of instructions
  # - Memory
  # - Stack
  # - Logic
  # - Math
  # - Control/Compare
  # - Move
  # - Call
  
  # Instruction derives from Code, for the assembly api
  
  class Instruction < Code    
    def initialize  options
      @attributes = options
    end
    attr_reader :attributes
    def opcode
      @attributes[:opcode]
    end
    #abstract, only should be called from derived
    def to_s
      atts = @attributes.dup
      atts.delete(:opcode)
      atts.delete(:update_status)
      atts.delete(:condition_code) if atts[:condition_code] == :al
      atts.empty? ? "" : ", #{atts}"
    end
    # returns an array of registers (RegisterReferences) that this instruction uses.
    # ie for r1 = r2 + r3 
    # which in assembler is add r1 , r2 , r3
    # it would return [r2,r3]
    # for pushes the list may be longer, whereas for a jump empty
    def uses
      raise "abstract called for #{self.class}"
    end
    # returns an array of registers (RegisterReferences) that this instruction assigns to.
    # ie for r1 = r2 + r3 
    # which in assembler is add r1 , r2 , r3
    # it would return [r1]
    # for most instruction this is one, but comparisons and jumps 0 , and pop's as long as 16
    def assigns
      raise "abstract called for #{self.class}"
    end
    def method_missing name , *args , &block 
      return super unless (args.length <= 1) or block_given?
      set , attribute = name.to_s.split("set_")
      if set == ""
        @attributes[attribute.to_sym] = args[0] || 1
        return self 
      else
        return super
      end
      return @attributes[name.to_sym]
    end
  end
  
  class StackInstruction < Instruction
    def initialize first , options = {}
      @first = first
      super(options)
    end
    # when calling we place a dummy push/pop in the stream and calculate later what registers actually need saving 
    def set_registers regs
      @first = regs.collect{ |r| r.symbol }
    end
    def is_push?
      opcode == :push
    end
    def is_pop?
      !is_push?
    end
    def uses
      is_push? ? regs : []
    end
    def assigns
      is_pop? ? regs : []
    end
    def regs
      @first
    end
    def to_s
      "#{opcode} [#{@first.collect {|f| f.to_asm}.join(',') }] #{super}"
    end
  end
  class MemoryInstruction < Instruction
    def initialize result , left , right = nil , options = {}
      @result = result
      @left = left
      @right = right
      super(options)
    end
    def uses
      ret = [@left.register ]
      ret << @right.register unless @right.nil?
      ret
    end
    def assigns
      [@result.register]
    end
  end
  class LogicInstruction < Instruction
    #  result = left op right
    # 
    # Logic instruction are your basic operator implementation. But unlike the (normal) code we write
    #    these Instructions must have "place" to write their results. Ie when you write 4 + 5 in ruby
    #    the result is sort of up in the air, but with Instructions the result must be assigned 
    def initialize result , left , right , options = {}
      @result = result
      @left = left
      @right = right.is_a?(Fixnum) ? IntegerConstant.new(right) : right
      super(options)
    end
    attr_accessor :result , :left ,  :right
    def uses
      ret = []
      ret << @left.register if @left and not @left.is_a? Constant
      ret << @right.register if @right and not @right.is_a?(Constant)
      ret
    end
    def assigns
      [@result.register]
    end
    def to_s
      "#{opcode} #{result.to_asm} , #{left.to_asm} , #{right.to_asm} #{super}"
    end
  end
  class CompareInstruction < Instruction
    def initialize left , right , options  = {}
      @left = left
      @right = right.is_a?(Fixnum) ? IntegerConstant.new(right) : right
      super(options)
    end
    def uses
      ret = [@left.register ]
      ret << @right.register unless @right.is_a? Constant
      ret
    end
    def assigns
      []
    end
    def to_s
      "#{opcode} #{@left.to_asm} , #{@right.to_asm} #{super}"
    end
  end
  class MoveInstruction < Instruction
    def initialize to , from , options = {}
      @to = to
      @from = from.is_a?(Fixnum) ? IntegerConstant.new(from) : from
      raise "move must have from set #{inspect}" unless from
      super(options)
    end
    attr_accessor :to , :from
    def uses
      @from.is_a?(Constant) ? [] : [@from.register]
    end
    def assigns
      [@to.register]
    end
    def to_s
      "#{opcode} #{@to.to_asm} , #{@from.to_asm} #{super}"
    end
  end
  class CallInstruction < Instruction
    def initialize first , options  = {}
      @first = first
      super(options)
      opcode = @attributes[:opcode].to_s
      if opcode.length == 3 and opcode[0] == "b"
        @attributes[:condition_code] = opcode[1,2].to_sym
        @attributes[:opcode] = :b
      end
      if opcode.length == 6 and opcode[0] == "c"
        @attributes[:condition_code] = opcode[4,2].to_sym
        @attributes[:opcode] = :call
      end
    end
    def uses
      if opcode == :call
        @first.args.collect {|arg| arg.register }
      else
        []
      end
    end
    def assigns
      if opcode == :call
        [RegisterReference.new(RegisterMachine.instance.return_register)]
      else
        []
      end
    end
    def to_s
      "#{opcode} #{@first.to_asm} #{super}"
    end
  end
end